/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "SkyDome.h"
#include "EnvironmentManager.h"
#include "CameraObject.h"

namespace PQEngine
{
	const Ogre::String SkyDome::SCENE_NODE_NAME = "Env/SkyDome/SceneNode";
	const Ogre::String SkyDome::ENTITY_NAME = "Env/SkyDome/Entity";
	const Ogre::String SkyDome::MESH_NAME = "Env/SkyDome/Mesh";
    const Ogre::String SkyDome::MATERIAL_NAME = "Env/SkyDome/Material";
	const Ogre::String SkyDome::MATERIAL_CLONE_NAME = "Env/SkyDome/MaterialClone";

	SkyDome::SkyDome(SceneObject* sceneObject,Type type)
		: _sceneObject(sceneObject),_type(type)
	{
		assert(_sceneObject);
		_sceneManager=_sceneObject->getSceneManager();
		_sceneManager->getRenderQueue()->getQueueGroup(SCENE_RENDER_QUEUE_SKY_DOME)->setShadowsEnabled(false);
		
		//init the material
		initMaterial();
		//setHaze(false);
		
		//generate sky dome geometry
		generateDomeGeometry();
		
		_entity=_sceneManager->createEntity(ENTITY_NAME,MESH_NAME);
		_entity->setMaterial(_material);
		_entity->setRenderQueueGroup(SCENE_RENDER_QUEUE_SKY_DOME);
		_entity->setCastShadows(false);

		//init scene node
		_sceneNode=_sceneObject->createCameraFollowSceneNode(SCENE_NODE_NAME);
		_sceneNode->attachObject(_entity);
		//_sceneNode->setInheritOrientation(false);
	}

	void SkyDome::initMaterial()
	{
		_material=Ogre::MaterialManager::getSingletonPtr()->getByName(MATERIAL_NAME);
		if(_material.isNull()){
			return;
		}
		//clone
		//_material=ptr->clone(MATERIAL_CLONE_NAME);
		//_material->load();
		_material->load();

		_shaderEnabled=_material->getBestTechnique()->getPass(0)->isProgrammable();
	}


	void SkyDome::generateDomeGeometry()
	{
		if(Ogre::MeshManager::getSingleton().resourceExists(MESH_NAME)){
			return;//Already exist
		}
		Ogre::LogManager::getSingleton().logMessage("Creating Sky dome mesh...");
		Ogre::MeshPtr mesh=Ogre::MeshManager::getSingleton().createManual(MESH_NAME,EnvironmentManager::ENV_GROUP_NAME);
		Ogre::SubMesh* submesh=mesh->createSubMesh();
		Ogre::VertexData* vertexData=new Ogre::VertexData();
		mesh->sharedVertexData=vertexData;

		Ogre::VertexDeclaration* vertexDecl=vertexData->vertexDeclaration;
		size_t offset=0;
		//position
		vertexDecl->addElement(0,offset,Ogre::VET_FLOAT3,Ogre::VES_POSITION);
		offset+=Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
		//normal
		vertexDecl->addElement(0,offset,Ogre::VET_FLOAT3,Ogre::VES_NORMAL);
		offset+=Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
		//texture coord
		vertexDecl->addElement(0, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
		offset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);

		 // Allocate the vertex buffer
        switch (_type) {
            case TYPE_SKY_DOME:
				vertexData->vertexCount=MESH_SEGMENTS*(MESH_SEGMENTS-1)+2;
                break;
            case TYPE_IMAGE_STAR_FIELD:
				vertexData->vertexCount=(MESH_SEGMENTS+1)*(MESH_SEGMENTS+1);
                break;
        }
		Ogre::HardwareVertexBufferSharedPtr vbuf=Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0),vertexData->vertexCount,Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		Ogre::VertexBufferBinding* binding=vertexData->vertexBufferBinding;
		binding->setBinding(0,vbuf);
		float* pVertex=static_cast<float*>(vbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));

		 // Allocate the index buffer
		switch (_type) {
            case TYPE_SKY_DOME:
                submesh->indexData->indexCount = 2 * MESH_SEGMENTS * (MESH_SEGMENTS - 1) * 3;
                break;
            case TYPE_IMAGE_STAR_FIELD:
                submesh->indexData->indexCount = 2 * (MESH_SEGMENTS - 1) * MESH_SEGMENTS * 3;
                break;
        };

		submesh->indexData->indexBuffer=Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT,submesh->indexData->indexCount,Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		Ogre::HardwareIndexBufferSharedPtr ibuf=submesh->indexData->indexBuffer;
		unsigned short *pIndices = static_cast<unsigned short*>(ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
		switch(_type){
			 case TYPE_SKY_DOME:
				 initDomeBuffer(pVertex,pIndices,MESH_SEGMENTS);
                break;
            case TYPE_IMAGE_STAR_FIELD:
				initStarFeildBuffer(pVertex,pIndices,MESH_SEGMENTS);
                break;
		}
		vbuf->unlock();
		ibuf->unlock();
		
		submesh->useSharedVertices=true;
		
		mesh->_setBounds(Ogre::AxisAlignedBox (-1, -1, -1, 1, 1, 1), false);
		mesh->_setBoundingSphereRadius(1);
		mesh->load();
		
		Ogre::LogManager::getSingleton ().logMessage("Generate dome geometry mesh done...");
	}

	void SkyDome::enable(bool enabled)
	{
		_sceneNode->setVisible(enabled);
		_enabled=enabled;
	}


	void SkyDome::setHaze(bool enabled)
	{
		_hazeEnabled=enabled;
		return;
		if(!_shaderEnabled){
			return;
		}

		Ogre::Pass* pass=_material->getTechnique(0)->getPass(0);
		if(_shaderEnabled){
			pass->setFragmentProgram("SkyDomeFP");
		}else{
			pass->setFragmentProgram("SkyDomeFP_NoHaze");
		}
		_vertexPtr=pass->getVertexProgramParameters();
		_fragmentPtr=pass->getFragmentProgramParameters();
		
		_property.setup(pass->getVertexProgramParameters(),pass->getFragmentProgramParameters());
      
	}

	void SkyDome::update(float deltaT)
	{
		if(!_enabled){
			return;
		}

		setFarRadius((_sceneObject->getCamera()->getFarClipDistance() + _sceneObject->getCamera()->getNearClipDistance()) / 2);
	}

	void SkyDome::initDomeBuffer(float *pVertex, unsigned short *pIndices, int segments)
    {
        const float deltaLatitude = Ogre::Math::PI / (float )segments;
        const float deltaLongitude = Ogre::Math::PI * 2.0 / (float )segments;

        // Generate the rings
        for (int i = 1; i < segments; i++) {
            float r0 = Ogre::Math::Sin (Ogre::Radian (i * deltaLatitude));
            float y0 = Ogre::Math::Cos (Ogre::Radian (i * deltaLatitude));

            for (int j = 0; j < segments; j++) {
                float x0 = r0 * Ogre::Math::Sin (Ogre::Radian (j * deltaLongitude));
                float z0 = r0 * Ogre::Math::Cos (Ogre::Radian (j * deltaLongitude));

                *pVertex++ = x0;
                *pVertex++ = y0;
                *pVertex++ = z0;

                *pVertex++ = -x0;
                *pVertex++ = -y0;
                *pVertex++ = -z0;

                *pVertex++ = 0;
                *pVertex++ = 1 - y0;
            }
        }

        // Generate the "north pole"
        *pVertex++ = 0;	// Position
        *pVertex++ = 1;
        *pVertex++ = 0;
        *pVertex++ = 0;	// Normal
        *pVertex++ = -1;
        *pVertex++ = 0;
        *pVertex++ = 0;	// UV
        *pVertex++ = 0;

        // Generate the "south pole"
        *pVertex++ = 0;	// Position
        *pVertex++ = -1;
        *pVertex++ = 0;
        *pVertex++ = 0;	// Normal
        *pVertex++ = 1;
        *pVertex++ = 0;
        *pVertex++ = 0;	// UV
        *pVertex++ = 2;

        // Generate the mid segments
        for (int i = 0; i < segments - 2; i++) {
            for (int j = 0; j < segments; j++) {
                *pIndices++ = segments * i + j;
                *pIndices++ = segments * i + (j + 1) % segments;
                *pIndices++ = segments * (i + 1) + (j + 1) % segments;
                *pIndices++ = segments * i + j;
                *pIndices++ = segments * (i + 1) + (j + 1) % segments;
                *pIndices++ = segments * (i + 1) + j;
            }
        }

        // Generate the upper cap
        for (int i = 0; i < segments; i++) {
            *pIndices++ = segments * (segments - 1);
            *pIndices++ = (i + 1) % segments;
            *pIndices++ = i;
        }

        // Generate the lower cap
        for (int i = 0; i < segments; i++) {
            *pIndices++ = segments * (segments - 1) + 1;
            *pIndices++ = segments * (segments - 2) + i;
            *pIndices++ = segments * (segments - 2) + (i + 1) % segments;
        }
    }

	void SkyDome::initStarFeildBuffer(float *pVertex, unsigned short *pIndices, int segments)
    {
        const float deltaLatitude = Ogre::Math::PI / (float )segments;
        const float deltaLongitude = Ogre::Math::PI * 2.0 / (float )segments;

        // Generate the rings
        for (int i = 0; i <= segments; i++) {
            float r0 = Ogre::Math::Sin (Ogre::Radian (i * deltaLatitude));
            float y0 = Ogre::Math::Cos (Ogre::Radian (i * deltaLatitude));

            for (int j = 0; j <= segments; j++) {
                float x0 = r0 * Ogre::Math::Sin (Ogre::Radian (j * deltaLongitude));
                float z0 = r0 * Ogre::Math::Cos (Ogre::Radian (j * deltaLongitude));

                *pVertex++ = x0;
                *pVertex++ = y0;
                *pVertex++ = z0;

                *pVertex++ = -x0;
                *pVertex++ = -y0;
                *pVertex++ = -z0;

                *pVertex++ = (float )j / (float )segments;
                *pVertex++ = 1 - (y0 * 0.5 + 0.5);
            }
        }

        // Generate the mid segments
        int vRowSize = segments + 1;
        for (int i = 1; i < segments; i++) {
            for (int j = 0; j < segments; j++) {
                int baseIdx = vRowSize * i + j;
                *pIndices++ = baseIdx;
                *pIndices++ = baseIdx + 1;
                *pIndices++ = baseIdx + vRowSize + 1;
                *pIndices++ = baseIdx + 1;
                *pIndices++ = baseIdx;
                *pIndices++ = baseIdx - vRowSize;
            }
        }
    }

	void SkyDome::setFarRadius(Ogre::Real radius)
	{
        _sceneNode->setScale(Ogre::Vector3::UNIT_SCALE * radius);
    }

	void SkyDome::setSunDirection(const Ogre::Vector3& sunDir)
	{
		float elevation = sunDir.dotProduct(Ogre::Vector3::UNIT_Y);
		elevation = elevation * 0.5 + 0.5;
		Ogre::Pass* pass = _material->getBestTechnique()->getPass(0);
		if (_shaderEnabled) {
			/*
			const Ogre::GpuConstantDefinition* def=_vertexPtr->_findNamedConstantDefinition("sunDirection");
			if(def){
				_vertexPtr->_writeRawConstant(def->physicalIndex, sunDir);
			}

			def=_fragmentPtr->_findNamedConstantDefinition("offset");
			if(def){
				_fragmentPtr->_writeRawConstant(def->physicalIndex, elevation);
			}
			*/
		} else {
			//Ogre::TextureUnitState* tex = pass->getTextureUnitState(0);
			//tex->setTextureUScroll(elevation);
		}
	}

	void SkyDome::setHazeColor(const Ogre::ColourValue& color)
	{
		if(_shaderEnabled&&_hazeEnabled){
			//_property.setHazeColor(color);
		}
	}

}
